/*
 * @(#) $Header$
 *
 * Copyright (C)  2013  Daniel Léonard
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */

package ca.forklabs.baselib.util.logging;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.text.MessageFormat;
import java.util.logging.Formatter;
import java.util.logging.Level;
import java.util.logging.LogManager;
import java.util.logging.LogRecord;
import java.util.logging.SimpleFormatter;

import static ca.forklabs.baselib.util.StringUtil.isBlank;

/**
 * Class {@code CompactLogFormatter} writes the log message on a single line
 * (instead of two for {@link SimpleFormatter}). Its format is always the same:
 * <blockquote>
 * {@code date time [single letter level] compact name - message}
 * </blockquote>
 * with exceptions being written on the next line with their full stack trace.
 * <p>
 * This formatter also provides a way to compact the name of its logger by
 * giving it a string to look for (the <em>long form</em>) and a replacement
 * string (the <em>short form</em>). For example, if logger
 * {@code ca.forklabs.baselib.util.logging.CompactLogFormatter} where to write a
 * log record, its full name would be written but by giving
 * {@code ca.forklabs.baselib.util.logging} as the long form and
 * {@code c.f.b.u.l} as the short form, the compact name would be
 * {@code c.f.b.u.l.CompactLogFormatter}.
 * <p>
 * To configure the long form and the short form in a properties file:
 * <blockquote><pre>
 * handlers = java.util.logging.ConsoleHandler
 * java.util.logging.ConsoleHandler.formatter = ca.forklabs.baselib.util.logging.CompactLogFormatter
 * ca.forklabs.baselib.util.logging.CompactLogFormatter.longForm = ca.forklabs.baselib.util.logging
 * ca.forklabs.baselib.util.logging.CompactLogFormatter.shortForm = c.f.b.u.l
 * </pre></blockquote>
 *
 * @author <a href="mailto:forklabs at gmail.com?subject=ca.forklabs.baselib.util.logging.CompactLogFormatter">Daniel Léonard</a>
 * @version $Revision$
 */
public class CompactLogFormatter extends Formatter {

//---------------------------
// Class variables
//---------------------------

   /** The suffix of the key indicating the long form. */
   public static final String LONG_FORM_KEY = "longForm"; //$NON-NLS-1$

   /** The suffix of the key indicating the compact (short) form. */
   public static final String SHORT_FORM_KEY = "shortForm"; //$NON-NLS-1$


//---------------------------
// Instance variables
//---------------------------

   /** The long form. */
   private String long_form = null;
   /** The short form. */
   private String short_form = null;


//---------------------------
// Constructors
//---------------------------

   /**
    * Constructor.
    * <p>
    * The values of the long form and short form are taken from the
    * configuration file.
    */
   @SuppressWarnings("hiding")
   public CompactLogFormatter() {
   // http://stackoverflow.com/questions/2710524/how-to-configure-my-own-formatter-in-java-logging-property-file
      LogManager logManager = LogManager.getLogManager();
      String name = this.getClass().getName();

      final String long_form_key = name + "." + LONG_FORM_KEY; //$NON-NLS-1$
      String long_form = logManager.getProperty(long_form_key);
      this.setLongForm(long_form);

      final String short_form_key = name + "." + SHORT_FORM_KEY; //$NON-NLS-1$
      String short_form = logManager.getProperty(short_form_key);
      this.setShortForm(short_form);
      }

   /**
    * Constructor.
    * @param   long_form   the long form.
    * @param   short_form   the short form.
    */
   public CompactLogFormatter(String long_form, String short_form) {
      this.setLongForm(long_form);
      this.setShortForm(short_form);
      }


//---------------------------
// Accessors and mutators
//---------------------------

   /**
    * Changes the long form.
    * @param   form   the new long form.
    */
   protected void setLongForm(String form) {
      this.long_form = form;
      }

   /**
    * Gets the long form.
    * @return   the long form.
    */
   public String getLongForm() {
      return this.long_form;
      }

   /**
    * Changes the short form.
    * @param   form   the new short form.
    */
   protected void setShortForm(String form) {
      this.short_form = form;
      }

   /**
    * Gets the short form.
    * @return   the short form.
    */
   public String getShortForm() {
      return this.short_form;
      }


//---------------------------
// Instance methods
//---------------------------

   /**
    * Formats the log level. The format is the first letter of the non-localized
    * name of the level.
    * @param   log_record   the log record.
    * @return   the string value.
    */
   protected String formatLevel(LogRecord log_record) {
      Level level = log_record.getLevel();
      String name = level.toString();
      String letter = name.substring(0, 1);
      return letter;
      }

   /**
    * Compacts the name of the logger. It does {@code s/long_form/short_form/}.
    * @param   log_record   the log record.
    * @return   the compact form.
    */
   @SuppressWarnings("hiding")
   protected String compactName(LogRecord log_record) {
      String long_form = this.getLongForm();
      String short_form = this.getShortForm();

      String compact_name = log_record.getLoggerName();
      if ((false == isBlank(long_form)) && (false == isBlank(short_form))) {
         compact_name = compact_name.replace(long_form, short_form);
         }
      return compact_name;
      }

   /**
    * Formats the exception with its stack trace.
    * @param   log_record   the log record.
    * @return   the string value.
    */
   protected String formatException(LogRecord log_record) {
      String text;

      Throwable t = log_record.getThrown();
      if (null != t) {
         StringWriter sw = new StringWriter();
         try (PrintWriter pw = new PrintWriter(sw)) {
            t.printStackTrace(pw);
            }
         text = sw.toString();
         }
      else {
         text = ""; //$NON-NLS-1$
         }

      return text;
      }


//---------------------------
// Implemented methods from java.util.logging.Formatter
//---------------------------

   /**
    * {@inheritDoc}
    */
   @Override
   @SuppressWarnings("boxing")
   public String format(LogRecord log_record) {
      long instant = log_record.getMillis();

      String level = this.formatLevel(log_record);

      String name = this.compactName(log_record);

      String message = this.formatMessage(log_record);

      final String key = "line.separator"; //$NON-NLS-1$
      String eol = System.getProperty(key);

      String stack_trace = this.formatException(log_record);

      String pattern = "{0,date} {0,time} [{1}] {2} - {3}{4}{5}"; //$NON-NLS-1$
      String text = MessageFormat.format(pattern, instant, level, name, message, eol, stack_trace);

      return text;
      }

   }
